﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace TestNugetWrapper
{
    class Program
    {
        static int Main(string[] args)
        {
            var commandName = args[0];
            var packageName = GetPackageName(args);
            var packagesDir = GetPackagesDir(args);
            var packagesUri = new Uri(packagesDir);
            var solutionDir = GetSolutionDir(args);
            var solutionUri = new Uri(solutionDir);
            var projectFile = GetProjectFile(ref args);
            var projectDir = Path.GetDirectoryName(projectFile);
            var packageConfigFile = Path.Combine(projectDir, "packages.config");
            var projectDirUri = new Uri(projectDir);
            var packagesRelativeUri = Utility.RelativePath(projectDir, packagesDir);

            var nugetReturnCode = NuGet.Program.Main(args);

            if (!HandlesCommand(commandName))
                return nugetReturnCode;

            if (nugetReturnCode != 0)
                return nugetReturnCode;

            var package = NugetPackage.LoadPackage(packageName, packagesDir, packagesRelativeUri);
            var references = package.GetReferences();

            Console.WriteLine("Add Project References");
            using (var project = new ProjectAdapter(projectFile))
            {
                foreach (var reference in references)
                {
                    project.AddReference(reference);
                }
            }

            Console.WriteLine("Add To Packages.config");
            var packagesConfig = new NuGet.PackageReferenceFile(packageConfigFile);
            if (packagesConfig.EntryExists(package.PackageName, package.SVersion))
                packagesConfig.DeleteEntry(package.PackageName, package.SVersion);

            packagesConfig.AddEntry(package.PackageName, package.SVersion, new System.Runtime.Versioning.FrameworkName(".NETFramework,Version=v4.0"));

            return nugetReturnCode;
        }

        static string GetPackageName(string[] args)
        {
            string[] commands = new string[] { "install" };
            int cmdIndex = 0;
            for(int i = 0;i < args.Length;i++)
            {
                if (commands.Contains(args[i]))
                {
                    cmdIndex = i;
                    break;
                }
            }

            return args[cmdIndex + 1];
        }

        static string GetSolutionDir(string[] args)
        {
            string solDir = "-solutionDir";
            int dirIndex = 0;

            for (int i = 0; i < args.Length; i++)
            {
                if (args[i].ToLower() == solDir.ToLower())
                {
                    dirIndex = i;
                    break;
                }
            }

            if (dirIndex == 0)
                throw new Exception("Param -solutionDir Not Found");

            var slnDir = args[dirIndex + 1];
            return slnDir;
        }

        static string GetPackagesDir(string[] args)
        {
            var slnDir = GetSolutionDir(args);
            return Path.Combine(slnDir, "packages");
        }

        static string GetProjectFile(ref string[] args)
        {
            string projDir = "-projectFile";
            int dirIndex = 0;

            for (int i = 0; i < args.Length; i++)
            {
                if (args[i].ToLower() == projDir.ToLower())
                {
                    dirIndex = i;
                    break;
                }
            }

            if (dirIndex == 0)
                throw new Exception("Param -projectFile Not Found");

            var csprojDir = args[dirIndex + 1];
            var copyArgs = args;
            args = copyArgs.Where(t => t != copyArgs[dirIndex] && t != copyArgs[dirIndex + 1]).ToArray(); 
            return csprojDir;
        }

        static bool HandlesCommand(string command)
        {
            switch (command.ToLower())
            {
                case "install":
                case "update":
                    return true;
                default:
                    return false;
            }
        }
    }

    internal class NugetPackage
    {
        internal static NugetPackage LoadPackage(string packageName, string packagesDirStr, string packageRelativeStr)
        {
            var package = new NugetPackage();
            package.PackageName = packageName;
            package.PackagesDirStr = packagesDirStr;
            package.Version = package.GetLatestVersion();
            package.PackageRelativeDirectory = packageRelativeStr;

            return package;
        }

        internal NuGet.SemanticVersion SVersion 
        {
            get
            {
                return new NuGet.SemanticVersion(this.Version);
            }
            set
            {
                if (value == null)
                    return;

                this.Version = value.ToString();
            }
        }
        internal string Version { get; set; }
        internal string PackageName { get; set; }
        internal string TargetFramework { get; set; }
        internal string PackageRelativeDirectory { get; set; }

        private string PackagesDirStr { get; set; }
        private DirectoryInfo PackagesDir 
        {
            get
            {
                return new DirectoryInfo(this.PackagesDirStr);
            }
            set
            {
                if(value == null)
                    return;

                this.PackagesDirStr = value.FullName;
            }
        }

        internal NugetPackage()
        {
            this.TargetFramework = "net40";
        }

        internal string GetLatestVersion()
        {
            var packageDirVersions = this.PackagesDir.GetDirectories()
                .Where(t => t.Name.ToLower().Contains(this.PackageName.ToLower()))
                .Select(t => this.GetDirectoryVersion(t))
                .OrderByDescending(t => t.Version);

            return packageDirVersions.FirstOrDefault().ToString();
        }

        internal IEnumerable<AssemblyReference> GetReferences()
        {
            var newList = new List<AssemblyReference>();
            var packageLibDir = Path.Combine(string.Format("{0}.{1}", this.PackageName, this.Version), "lib");

            var libDirectory = new DirectoryInfo(Path.Combine(this.PackagesDirStr, packageLibDir));
            if (!libDirectory.Exists)
                return newList;

            var libDllFiles = libDirectory.GetFiles("*.dll", SearchOption.TopDirectoryOnly);
            foreach (var libFile in libDllFiles)
            {
                newList.Add(new AssemblyReference(Path.GetFileNameWithoutExtension(libFile.Name), Path.Combine(this.PackageRelativeDirectory, packageLibDir, libFile.Name)));
            }

            var targetFrameworkDir = new DirectoryInfo(Path.Combine(libDirectory.FullName, this.TargetFramework));
            var packageTargetDir = Path.Combine(packageLibDir, this.TargetFramework);

            if(targetFrameworkDir.Exists)
            {
                var targetDllFiles = targetFrameworkDir.GetFiles("*.dll", SearchOption.TopDirectoryOnly);
                foreach(var targetFile in targetDllFiles)
                {
                    newList.Add(new AssemblyReference(Path.GetFileNameWithoutExtension(targetFile.Name), Path.Combine(this.PackageRelativeDirectory, packageTargetDir, targetFile.Name)));
                }
            }

            return newList;
        }

        private NuGet.SemanticVersion GetDirectoryVersion(DirectoryInfo packageDir)
        {
            var versionTag = packageDir.Name.ToLower().Replace(this.PackageName + ".", string.Empty);
            return new NuGet.SemanticVersion(versionTag);
        }
    }

    internal class AssemblyReference
    {
        internal string AssemblyName { get; set; }
        internal string HintPath { get; set; }

        internal AssemblyReference(string assemblyName, string hintPath)
        {
            this.AssemblyName = assemblyName;
            this.HintPath = hintPath;
        }
    }
}
